// 
// Copyright (c) 2013 Jason Bell
// 
// Permission is hereby granted, free of charge, to any person obtaining a 
// copy of this software and associated documentation files (the "Software"), 
// to deal in the Software without restriction, including without limitation 
// the rights to use, copy, modify, merge, publish, distribute, sublicense, 
// and/or sell copies of the Software, and to permit persons to whom the 
// Software is furnished to do so, subject to the following conditions:
// 
// The above copyright notice and this permission notice shall be included 
// in all copies or substantial portions of the Software.
// 
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 
// DEALINGS IN THE SOFTWARE.
// 

using System;
using System.Runtime.InteropServices;
using System.Windows.Forms;

namespace Gharen.GLCore.X11
{
    public class X11Window
        : Window
    {
        IntPtr display;
        IntPtr handle;
        System.Collections.Generic.Dictionary<XKey, Keys> keyTable;
        IntPtr atom;
        bool created = true;

        public X11Window(IntPtr display, IntPtr handle)
        {
            this.display = display;
            this.handle = handle;

            this.atom = X.InternAtom(this.display, "WM_DELETE_WINDOW", true);
            IntPtr[] atoms = { atom };
            X.SetWMProtocols(this.display, this.handle, atoms, 1);

            X.Flush(this.display);

            keyTable = new System.Collections.Generic.Dictionary<XKey, Keys>();

            keyTable.Add(XKey.Escape, Keys.Escape);
            keyTable.Add(XKey.Return, Keys.Enter);
            keyTable.Add(XKey.space, Keys.Space);
            keyTable.Add(XKey.BackSpace, Keys.Back);

            keyTable.Add(XKey.Shift_L, Keys.LShiftKey);
            keyTable.Add(XKey.Shift_R, Keys.RShiftKey);
            keyTable.Add(XKey.Alt_L, Keys.Alt);
            keyTable.Add(XKey.Alt_R, Keys.Alt);
            keyTable.Add(XKey.Control_L, Keys.LControlKey);
            keyTable.Add(XKey.Control_R, Keys.RControlKey);
            keyTable.Add(XKey.Super_L, Keys.LWin);
            keyTable.Add(XKey.Super_R, Keys.RWin);
            keyTable.Add(XKey.Meta_L, Keys.LWin);
            keyTable.Add(XKey.Meta_R, Keys.RWin);
            keyTable.Add(XKey.ISO_Level3_Shift, Keys.Alt); // Normally AltGr

            keyTable.Add(XKey.Menu, Keys.Menu);
            keyTable.Add(XKey.Tab, Keys.Tab);
            keyTable.Add(XKey.minus, Keys.OemMinus);
            keyTable.Add(XKey.plus, Keys.Oemplus);
            keyTable.Add(XKey.equal, Keys.Oemplus);

            keyTable.Add(XKey.Caps_Lock, Keys.CapsLock);
            keyTable.Add(XKey.Num_Lock, Keys.NumLock);

            for (int i = (int)XKey.F1; i <= (int)XKey.F35; i++)
            {
                keyTable.Add((XKey)i, (Keys)((int)Keys.F1 + (i - (int)XKey.F1)));
            }

            for (int i = (int)XKey.a; i <= (int)XKey.z; i++)
            {
                keyTable.Add((XKey)i, (Keys)((int)Keys.A + (i - (int)XKey.a)));
            }

            for (int i = (int)XKey.A; i <= (int)XKey.Z; i++)
            {
                keyTable.Add((XKey)i, (Keys)((int)Keys.A + (i - (int)XKey.A)));
            }

            for (int i = (int)XKey.Number0; i <= (int)XKey.Number9; i++)
            {
                keyTable.Add((XKey)i, (Keys)((int)Keys.D0 + (i - (int)XKey.Number0)));
            }

            keyTable.Add(XKey.KP_0, Keys.NumPad0);
            keyTable.Add(XKey.KP_1, Keys.NumPad1);
            keyTable.Add(XKey.KP_2, Keys.NumPad2);
            keyTable.Add(XKey.KP_3, Keys.NumPad3);
            keyTable.Add(XKey.KP_4, Keys.NumPad4);
            keyTable.Add(XKey.KP_5, Keys.NumPad5);
            keyTable.Add(XKey.KP_6, Keys.NumPad6);
            keyTable.Add(XKey.KP_7, Keys.NumPad7);
            keyTable.Add(XKey.KP_8, Keys.NumPad8);
            keyTable.Add(XKey.KP_9, Keys.NumPad9);


            keyTable.Add(XKey.Pause, Keys.Pause);
            keyTable.Add(XKey.Break, Keys.Pause);
            keyTable.Add(XKey.Scroll_Lock, Keys.Pause);
            keyTable.Add(XKey.Insert, Keys.Insert);
            keyTable.Add(XKey.Print, Keys.PrintScreen);
            keyTable.Add(XKey.Sys_Req, Keys.PrintScreen);

            keyTable.Add(XKey.backslash, Keys.OemBackslash);
            keyTable.Add(XKey.bar, Keys.OemBackslash);
            keyTable.Add(XKey.braceleft, Keys.OemOpenBrackets);
            keyTable.Add(XKey.bracketleft, Keys.OemOpenBrackets);
            keyTable.Add(XKey.braceright, Keys.OemCloseBrackets);
            keyTable.Add(XKey.bracketright, Keys.OemCloseBrackets);
            keyTable.Add(XKey.colon, Keys.OemSemicolon);
            keyTable.Add(XKey.semicolon, Keys.OemSemicolon);
            keyTable.Add(XKey.quoteright, Keys.OemQuotes);
            keyTable.Add(XKey.quotedbl, Keys.OemQuotes);
            keyTable.Add(XKey.quoteleft, Keys.Oemtilde);
            keyTable.Add(XKey.asciitilde, Keys.Oemtilde);

            keyTable.Add(XKey.comma, Keys.Oemcomma);
            keyTable.Add(XKey.less, Keys.Oemcomma);
            keyTable.Add(XKey.period, Keys.OemPeriod);
            keyTable.Add(XKey.greater, Keys.OemPeriod);
            keyTable.Add(XKey.slash, Keys.OemBackslash);
            keyTable.Add(XKey.question, Keys.OemQuestion);

            keyTable.Add(XKey.Left, Keys.Left);
            keyTable.Add(XKey.Down, Keys.Down);
            keyTable.Add(XKey.Right, Keys.Right);
            keyTable.Add(XKey.Up, Keys.Up);

            keyTable.Add(XKey.Delete, Keys.Delete);
            keyTable.Add(XKey.Home, Keys.Home);
            keyTable.Add(XKey.End, Keys.End);
            keyTable.Add(XKey.Page_Up, Keys.PageUp);
            keyTable.Add(XKey.Page_Down, Keys.PageDown);

            keyTable.Add(XKey.KP_Add, Keys.Add);
            keyTable.Add(XKey.KP_Subtract, Keys.Subtract);
            keyTable.Add(XKey.KP_Multiply, Keys.Multiply);
            keyTable.Add(XKey.KP_Divide, Keys.Divide);
            keyTable.Add(XKey.KP_Decimal, Keys.Decimal);
            keyTable.Add(XKey.KP_Insert, Keys.NumPad0);
            keyTable.Add(XKey.KP_End, Keys.NumPad1);
            keyTable.Add(XKey.KP_Down, Keys.NumPad2);
            keyTable.Add(XKey.KP_Page_Down, Keys.NumPad3);
            keyTable.Add(XKey.KP_Left, Keys.NumPad4);
            keyTable.Add(XKey.KP_Right, Keys.NumPad6);
            keyTable.Add(XKey.KP_Home, Keys.NumPad7);
            keyTable.Add(XKey.KP_Up, Keys.NumPad8);
            keyTable.Add(XKey.KP_Page_Up, Keys.NumPad9);
            keyTable.Add(XKey.KP_Begin, Keys.NumPad5);
            keyTable.Add(XKey.KP_Delete, Keys.Decimal);
            keyTable.Add(XKey.KP_Enter, Keys.Enter);

            int first = 0, last = 0;
            X.DisplayKeycodes(display, ref first, ref last);
        }

        public override void Dispose()
        {
            // Currently, disposal is handled by X11Context.
        }

        public override void DoEvents()
        {
            XEvent e = new XEvent(); ;

            while (X.XPending(this.display) > 0)
            {
                X.XNextEvent(this.display, ref e);
                switch (e.type)
                {
                    case XEventName.ButtonPress:
                        TriggerMouseDown(ConvertButton(e.ButtonEvent.button), e.ButtonEvent.x, e.ButtonEvent.y);
                        break;
                    case XEventName.ButtonRelease:
                        TriggerMouseUp(ConvertButton(e.ButtonEvent.button), e.ButtonEvent.x, e.ButtonEvent.y);
                        break;
                    case XEventName.MotionNotify:
                        TriggerMouseMove(e.MotionEvent.x, e.MotionEvent.y);
                        break;
                    case XEventName.KeyPress:
                        //int keySym = X.XKeycodeToKeysym(this.display, (byte)e.KeyEvent.keycode, 0);
                        //Console.WriteLine((XKey)keySym);
                        TriggerKeyDown(ConvertKey(e.KeyEvent.keycode));
                        break;
                    case XEventName.KeyRelease:
                        TriggerKeyUp(ConvertKey(e.KeyEvent.keycode));
                        break;
                    case XEventName.ConfigureNotify:
                        TriggerResize();
                        break;
                    case XEventName.FocusIn:
                        TriggerGainedFocus();
                        break;
                    case XEventName.FocusOut:
                        TriggerLostFocus();
                        break;
                    case XEventName.ClientMessage:
                        this.created = false;
                        break;
                }
            }
        }

        MouseButtons ConvertButton(int button)
        {
            switch (button)
            {
                case 1:
                    return MouseButtons.Left;
                case 2:
                    return MouseButtons.Middle;
                case 3:
                    return MouseButtons.Right;
                case 8:
                    return MouseButtons.XButton1;
                case 9:
                    return MouseButtons.XButton2;
                default:
                    return MouseButtons.None;
            }
        }

        public override object Handle
        {
            get
            {
                return this.handle;
            }
        }

        public override bool IsCreated
        {
            get
            {
                return this.created;
            }
        }

        public override String Text
        {
            get
            {
                IntPtr name = IntPtr.Zero;

                X.FetchName(this.display, this.handle, ref name);
                if (name != IntPtr.Zero)
                    return Marshal.PtrToStringAnsi(name);

                return String.Empty;
            }
            set
            {
                X.StoreName(this.display, this.handle, value);
            }
        }

        Keys ConvertKey(int keycode)
        {
            int keySym = X.XKeycodeToKeysym(this.display, (byte)keycode, 0);
            if (this.keyTable.ContainsKey((XKey)keySym))
                return this.keyTable[(XKey)keySym];
            else
                return Keys.None;
        }
    }
}

